/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package tx

import (
	"encoding/hex"
	"fmt"
	"log"
	"math/rand"
	"net/rpc/jsonrpc"
	"runtime"
	"time"

	"github.com/spf13/cobra"
	"go.uber.org/atomic"
	"hundsun.com/hsl/hschain/cmd/cli/common"
	"hundsun.com/hsl/hschain/common/keystore"
	"hundsun.com/hsl/hschain/protos/ledger"
	service "hundsun.com/hsl/hschain/rpc/impl"
)

func transferContinueCmd() *cobra.Command {
	cmd := &cobra.Command{
		Hidden: true,
		Use:    "transferContinue",
		Short:  "Transfer continue",
		Long:   "Transfer continue",
		RunE: func(cmd *cobra.Command, args []string) error {
			ttl, _ := cmd.Flags().GetUint32(common.FlagTTL)
			fee, _ := cmd.Flags().GetUint64(common.FlagFee)
			chainID, _ := cmd.Flags().GetString(common.FlagChainID)
			execTy, _ := cmd.Flags().GetString(common.FlagExecTy)
			txPerSecond, _ := cmd.Flags().GetUint32(flagTxPerSecond)
			rpcAddr, _ := cmd.Flags().GetString(common.FlagRPCLaddr)
			client, err := jsonrpc.Dial("tcp", rpcAddr)
			if err != nil {
				return err
			}
			fmt.Printf("txPerSecond:%d", txPerSecond)
			fmt.Printf("ttl:%d", ttl)

			common.InitCryptoOpt(cmd)
			keystoreFilePath, _ := cmd.Flags().GetString(common.FlagKeyStoreFilePath)
			keystorePasswd, _ := cmd.Flags().GetString(common.FlagKeyStoreFilePasswd)
			keystore, err := keystore.LoadKeyStore(keystoreFilePath, keystorePasswd)
			if err != nil {
				return fmt.Errorf("load key store failed, err: %s", err.Error())
			}

			txChans := make(chan *ledger.Transaction, txPerSecond)
			numCPU := runtime.NumCPU() * 10

			var i atomic.Uint32
			for c := 0; c < numCPU; c++ {
				go func() {
					fmt.Printf("chan %d send tx\n", c)
					st := time.Now()
					for tx := range txChans {
						{
							i.Inc()
							st2 := time.Since(st)
							if i.Load() > txPerSecond && st2 < time.Second {
								fmt.Printf("sleep time:%d and len of txChans: %d\n", time.Second-st2, len(txChans))
								time.Sleep(time.Second - st2)
								st = time.Now()
								i.Store(0)
							}

							if st2 > time.Second {
								st = time.Now()
							}

							param, _ := common.AssembleGrpcRequest(service.ReceiveTransaction, tx)
							var result service.JSONRPCResp
							err = client.Call("JSONRPCServer.ProcessRPCRequest", param, &result)
							if err != nil {
								fmt.Printf("send tx error:%v", err)
							}
							fmt.Printf("send tx: %s success\n", hex.EncodeToString(tx.Hash()))
						}
					}
				}()
				time.Sleep(time.Millisecond * 10)
			}

			for n := 0; n < numCPU; n++ {
				fmt.Printf("chan %d build tx", n)
				go func() {
					for {
						index := rand.Intn(len(keystore.GetAllAccounts()) - 1)
						fromAccount := keystore.GetAllAccounts()[index]
						index = rand.Intn(len(keystore.GetAllAccounts()) - 1)
						toAccout := keystore.GetAllAccounts()[index]
						tx, err := common.BuildTransferTx(execTy, fromAccount, chainID, string(toAccout.GetAddress()), 1, fee, ttl, nil, nil)
						if err != nil {
							fmt.Printf("build tx err: %s\n", err.Error())
							continue
						}
						txChans <- tx
					}
				}()
			}
			select {}
		},
	}

	opt := []common.CmdOption{
		common.WithHiddenFlag(common.FlagFrom),
	}
	common.AddTransactionPublicFlag(cmd, opt...)
	cmd.Flags().Uint32(flagTxPerSecond, 50, "number of transactions per second")
	return cmd
}

func transferCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:           "transfer",
		Short:         "Send transaction",
		Long:          "Send transaction",
		RunE:          transfer,
		SilenceErrors: true,
	}

	opts := []common.CmdOption{
		common.WithRequiredFlag(common.FlagTo),
		common.WithRequiredFlag(common.FlagAmount),
	}
	common.AddTransactionPublicFlag(cmd, opts...)

	return cmd
}

func transfer(cmd *cobra.Command, args []string) error {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}()

	execTy, _ := cmd.Flags().GetString(common.FlagExecTy)
	validateOps := []common.CmdOption{
		common.WithCmdValidator(common.FlagAmount, common.NotNil),
		common.WithCmdValidator(common.FlagTo, common.Address),
	}
	tx, err := common.BuildTxWithCmd(cmd, execTy, nil, nil, validateOps...)
	if err != nil {
		return err
	}

	rpcLaddr, _ := cmd.Flags().GetString(common.FlagRPCLaddr)
	common.RPCCall(rpcLaddr, service.ReceiveTransaction, tx, func(result interface{}) {
		log.Printf("transaction '%s' sent successfully", hex.EncodeToString(tx.Hash()))
	})
	return nil
}
